Expand description
Stores are a primitive for creating deeply-nested reactive state, based on reactive_graph
.
Reactive signals allow you to define atomic units of reactive state. However, signals are imperfect as a mechanism for tracking reactive change in structs or collections, because they do not allow you to track access to individual struct fields or individual items in a collection, rather than the struct as a whole or the collection as a whole. Reactivity for individual fields can be achieved by creating a struct of signals, but this has issues; it means that a struct is no longer a plain data structure, but requires wrappers on each field.
Stores attempt to solve this problem by allowing arbitrarily-deep access to the fields of some data structure, while still maintaining fine-grained reactivity.
The Store
macro adds getters and setters for the fields of a struct. Call those getters or
setters on a reactive Store
or ArcStore
, or to a subfield, gives you
access to a reactive subfield. This value of this field can be accessed via the ordinary signal
traits (Get
, Set
, and so on).
The Patch
macro allows you to annotate a struct such that stores and fields have a
.patch()
method, which allows you to provide an entirely new value, but only
notify fields that have changed.
Updating a field will notify its parents and children, but not its siblings.
Stores can therefore
- work with plain Rust data types, and
- provide reactive access to individual fields
§Example
use reactive_graph::{
effect::Effect,
traits::{Read, Write},
};
use reactive_stores::{Patch, Store};
#[derive(Debug, Store, Patch, Default)]
struct Todos {
user: String,
todos: Vec<Todo>,
}
#[derive(Debug, Store, Patch, Default)]
struct Todo {
label: String,
completed: bool,
}
let store = Store::new(Todos {
user: "Alice".to_string(),
todos: Vec::new(),
});
Effect::new(move |_| {
// you can access individual store withs field a getter
println!("todos: {:?}", &*store.todos().read());
});
// won't notify the effect that listen to `todos`
store.todos().write().push(Todo {
label: "Test".to_string(),
completed: false,
});
§Implementation Notes
Every struct field can be understood as an index. For example, given the following definition
#[derive(Debug, Store, Patch, Default)]
struct Name {
first: String,
last: String,
}
We can think of first
as 0
and last
as 1
. This means that any deeply-nested field of a
struct can be described as a path of indices. So, for example:
#[derive(Debug, Store, Patch, Default)]
struct User {
user: Name,
}
#[derive(Debug, Store, Patch, Default)]
struct Name {
first: String,
last: String,
}
Here, given a User
, first
can be understood as [0
, 0
] and last
is [0
, 1
].
This means we can implement a store as the combination of two things:
- An
Arc<RwLock<T>>
that holds the actual value - A map from field paths to reactive “triggers,” which are signals that have no value but track reactivity
Accessing a field via its getters returns an iterator-like data structure that describes how to
get to that subfield. Calling .read()
returns a guard that dereferences to the value of that
field in the signal inner Arc<RwLock<_>>
, and tracks the trigger that corresponds with its
path; calling .write()
returns a writeable guard, and notifies that same trigger.
Structs§
- Reference-counted access to a single field of type
T
. - A reference-counted container for a reactive store.
- Provides access to the data at some index in another collection.
- Gives access to the value in a collection based on some key.
- Wraps access to a single field of type
T
. - A map of the keys for a keyed subfield.
- Provides access to a subfield that contains some kind of keyed collection.
- Gives keyed write access to a value in some collection.
- An arena-allocated container for a reactive store.
- An iterator over the values in a collection, as reactive fields.
- An iterator over a
KeyedSubfield
. - The reactive trigger that can be used to track updates to a store field.
- The path of a field within some store.
- One segment of a
StorePath
. - Accesses a single field of a reactive structure.
Traits§
- Extends optional store fields, with the ability to unwrap or map over them.
- Allows updating a store or field in place with a new value.
- Allows patching a store field with some new value.
- Describes a type that can be accessed as a reactive store field.
- Provides unkeyed reactive access to the fields of some collection.